--- title: "01-造型生成网站 - 设计稿" created: 2026-01-30 aliases: - 造型生成网站 - 设计稿 tags: - 项目 --- # 造型生成网站 - 设计稿 ## 1. 目标与范围 ### 1.1 项目目标 - **核心目标**:用户上传照片,系统生成"新造型图像 + 造型理由",形成可展示的前后对比与文字说明 - **技术目标**:构建可扩展的 AI 模型调用架构,支持灵活切换不同 Vision LLM 和生图模型 ### 1.2 MVP 范围 | **包含** | **不包含(后期迭代)** | | --- | --- | | 单人头像/半身照输入 | 多人合照处理 | | 1 张新造型图 + 1 段中文说明 | 多风格批量生成 | | 基础上传与展示 | 复杂编辑工具 | | 模型可配置切换 | 用户账号体系 | | 临时图片存储 | 历史记录持久化 | --- ## 2. 用户流程(前台) ### 2.1 主流程 ![[image-d1e6919c.png]] ### 2.2 页面结构 ```text / # 首页(上传入口) /result/:taskId # 结果页(支持分享链接) ``` --- ## 3. 系统架构(后台) ### 3.1 整体架构图 ![[diagram-1769715951544-7a7cd582.png]] ### 3.2 核心组件说明 | **组件** | **职责** | **技术选型** | | --- | --- | --- | | Frontend | 上传、进度展示、结果渲染 | Next.js 14 + React 18 + TailwindCSS | | Backend API | 请求处理、任务编排、结果返回 | Node.js + Express + TypeScript | | Task Orchestrator | 编排 AI 调用流程 | 自研状态机 | | Model Adapter | 统一模型调用接口 | 适配器模式 | | Image Processor | 图片压缩、格式转换、裁剪 | sharp | | Storage Service | 临时文件存储 | 本地文件系统 / S3 兼容存储 | --- ## 4. 模型配置设计(核心) ### 4.1 配置文件结构 YAML ```yaml # config/models.yaml # ============ Vision LLM 配置 ============ vision: # 当前激活的 provider active: "openai" providers: openai: name: "GPT-4o" endpoint: "https://api.openai.com/v1/chat/completions" model: "gpt-4o" apiKeyEnv: "OPENAI_API_KEY" maxTokens: 2000 temperature: 0.7 timeout: 60000 anthropic: name: "Claude 3.5 Sonnet" endpoint: "https://api.anthropic.com/v1/messages" model: "claude-3-5-sonnet-20241022" apiKeyEnv: "ANTHROPIC_API_KEY" maxTokens: 2000 timeout: 60000 google: name: "Gemini 1.5 Pro" endpoint: "https://generativelanguage.googleapis.com/v1beta/models" model: "gemini-1.5-pro" apiKeyEnv: "GOOGLE_API_KEY" maxTokens: 2000 timeout: 60000 alibaba: name: "Qwen-VL-Max" endpoint: "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation" model: "qwen-vl-max" apiKeyEnv: "DASHSCOPE_API_KEY" maxTokens: 2000 timeout: 60000 # ============ 图像生成模型配置 ============ imageGen: # 当前激活的 provider active: "openai" providers: openai: name: "DALL-E 3" endpoint: "https://api.openai.com/v1/images/generations" model: "dall-e-3" apiKeyEnv: "OPENAI_API_KEY" size: "1024x1024" quality: "hd" timeout: 120000 replicate_flux: name: "Flux 1.1 Pro" endpoint: "https://api.replicate.com/v1/predictions" model: "black-forest-labs/flux-1.1-pro" apiKeyEnv: "REPLICATE_API_TOKEN" aspectRatio: "1:1" outputFormat: "webp" timeout: 120000 replicate_sdxl: name: "SDXL + InstantID" endpoint: "https://api.replicate.com/v1/predictions" model: "zsxkib/instant-id" apiKeyEnv: "REPLICATE_API_TOKEN" timeout: 180000 # 需要额外传入 face_image requiresFaceImage: true fal_flux: name: "Fal Flux Pro" endpoint: "https://fal.run/fal-ai/flux-pro" apiKeyEnv: "FAL_KEY" imageSize: "square_hd" timeout: 120000 midjourney: name: "Midjourney (via Proxy)" endpoint: "${MIDJOURNEY_PROXY_URL}" apiKeyEnv: "MIDJOURNEY_API_KEY" timeout: 300000 # MJ 需要轮询获取结果 pollingMode: true pollingInterval: 5000 # ============ 人脸检测配置(可选)============ faceDetection: enabled: true provider: "local" # local / cloud providers: local: # 使用 face-api.js modelPath: "./models/face-api" minConfidence: 0.5 cloud: # 使用云服务 endpoint: "${FACE_API_ENDPOINT}" apiKeyEnv: "FACE_API_KEY" # ============ 通用配置 ============ common: # 请求重试 retry: maxAttempts: 3 backoffMs: 1000 backoffMultiplier: 2 # 并发限制 rateLimit: maxConcurrent: 10 requestsPerMinute: 30 ``` ### 4.2 环境变量配置 ```properties # .env.example # ===== Vision LLM API Keys ===== OPENAI_API_KEY=sk-xxx ANTHROPIC_API_KEY=sk-ant-xxx GOOGLE_API_KEY=AIza-xxx DASHSCOPE_API_KEY=sk-xxx # ===== Image Generation API Keys ===== REPLICATE_API_TOKEN=r8_xxx FAL_KEY=xxx MIDJOURNEY_PROXY_URL=https://your-mj-proxy.com MIDJOURNEY_API_KEY=xxx # ===== Optional Services ===== FACE_API_ENDPOINT= FACE_API_KEY= # ===== Storage ===== STORAGE_TYPE=local # local / s3 STORAGE_PATH=./uploads # S3_BUCKET=xxx # S3_REGION=xxx # S3_ACCESS_KEY=xxx # S3_SECRET_KEY=xxx # ===== Server ===== PORT=3001 NODE_ENV=development ``` ### 4.3 模型适配器接口设计 ```typescript // types/models.ts // ===== Vision LLM 接口 ===== interface VisionAnalysisRequest { imageBase64: string; mimeType: string; prompt: string; } interface VisionAnalysisResponse { imagePrompt: string; // 提取的 ###IMAGE_PROMPT### reasoning: string; // 提取的 ###REASONING### rawResponse: string; // 原始响应(调试用) usage?: { promptTokens: number; completionTokens: number; }; interface VisionProvider { name: string; analyze(request: VisionAnalysisRequest): Promise; healthCheck(): Promise; } // ===== Image Generation 接口 ===== interface ImageGenerationRequest { prompt: string; referenceImageBase64?: string; // 用于人脸一致性模型 size?: string; style?: string; } interface ImageGenerationResponse { imageUrl: string; // 生成图片 URL imageBase64?: string; // 可选返回 base64 revisedPrompt?: string; // 模型修正后的提示词 } interface ImageGenProvider { name: string; generate(request: ImageGenerationRequest): Promise; healthCheck(): Promise; } ``` ### 4.4 模型工厂模式 TypeScript ```typescript // services/modelFactory.ts class ModelFactory { private visionProviders: Map; private imageGenProviders: Map; private config: ModelConfig; constructor(configPath: string) { this.config = this.loadConfig(configPath); this.visionProviders = new Map(); this.imageGenProviders = new Map(); this.initializeProviders(); } // 获取当前激活的 Vision 提供者 getVisionProvider(): VisionProvider { const active = this.config.vision.active; return this.visionProviders.get(active); } // 获取当前激活的生图提供者 getImageGenProvider(): ImageGenProvider { const active = this.config.imageGen.active; return this.imageGenProviders.get(active); } // 动态切换提供者(运行时) switchVisionProvider(providerName: string): void; switchImageGenProvider(providerName: string): void; // 获取所有可用提供者(用于管理界面) listProviders(): { vision: string[], imageGen: string[] }; } ``` --- ## 5. 核心流程设计 ### 5.1 主流程时序图 ![[diagram-1769716264901-33c82a37.png]] ### 5.2 任务状态机 ![[diagram-1769716390560-900198c8.png]] --- ## 6. 元提示词设计 ### 6.1 系统提示词模板 ```yaml # config/prompts.yaml systemPrompt: | 你是一位世界顶级的发型设计师与形象顾问,拥有20年服务名人与普通客户的经验。 你擅长根据客户的脸型、五官特征、气质类型,设计最适合的发型与整体造型方案。 ## 你的任务 分析用户上传的照片,为其设计一个全新的造型方案,并提供专业的设计理由。 ## 分析维度 1. **脸型分析**:椭圆/圆形/方形/长形/心形/菱形 2. **五官特征**:眼睛大小、鼻型、嘴唇、额头高度、下颌线条 3. **当前状态**:现有发型、发质推测、整体风格 4. **气质类型**:知性/甜美/帅气/成熟/清新/... ## 输出要求 请严格按照以下格式输出,使用分隔符分隔: ###IMAGE_PROMPT### (这里输出英文的图像生成提示词,要求: - 必须强调 "same person, preserve exact facial features, same face" - 详细描述新发型:长度、层次、刘海、颜色、质感 - 描述服装风格(如适用) - 指定摄影风格:lighting, camera angle, background - 指定图像质量:professional photography, 8k, detailed - 示例结构:A portrait of the same person with [新发型描述], wearing [服装], [摄影风格], [质量词]) ###REASONING### (这里输出中文的造型设计说明,包含: - 脸型与五官分析结果 - 为什么推荐这个发型(解决什么问题/强化什么优点) - 新造型会带来的气质变化 - 日常打理建议(可选) - 总字数控制在 150-250 字) userPrompt: | 请分析这张照片中的人物,为 TA 设计一个全新的造型方案。 ``` ### 6.2 输出解析器 ```typescript // utils/promptParser.ts interface ParsedResponse { imagePrompt: string; reasoning: string; parseSuccess: boolean; errors?: string[]; } function parseVisionResponse(rawResponse: string): ParsedResponse { const imagePromptMatch = rawResponse.match( /###IMAGE_PROMPT###\s*([\s\S]*?)(?=###REASONING###|$)/ ); const reasoningMatch = rawResponse.match( /###REASONING###\s*([\s\S]*?)$/ ); const result: ParsedResponse = { imagePrompt: imagePromptMatch?.[1]?.trim() || '', reasoning: reasoningMatch?.[1]?.trim() || '', parseSuccess: true, errors: [] }; // 验证 if (!result.imagePrompt) { result.parseSuccess = false; result.errors.push('Missing IMAGE_PROMPT section'); } if (!result.reasoning) { result.parseSuccess = false; result.errors.push('Missing REASONING section'); } return result; } ``` --- ## 7. API 接口设计 ### 7.1 接口清单 ```yaml # API Endpoints POST /api/upload: description: 上传图片并创建任务 request: type: multipart/form-data fields: image: File (required, max 10MB, jpg/png/webp) response: 200: taskId: string status: "pending" message: "任务已创建" 400: error: "INVALID_IMAGE" | "NO_FACE_DETECTED" | "MULTIPLE_FACES" message: string 429: error: "RATE_LIMITED" message: string GET /api/status/:taskId: description: 查询任务状态 response: 200: taskId: string status: "pending" | "analyzing" | "generating" | "completed" | "failed" progress: number (0-100) message: string resultUrl?: string # completed 时返回 error?: string # failed 时返回 404: error: "TASK_NOT_FOUND" GET /api/result/:taskId: description: 获取任务结果 response: 200: taskId: string originalImageUrl: string generatedImageUrl: string reasoning: string createdAt: string expiresAt: string 404: error: "TASK_NOT_FOUND" | "RESULT_EXPIRED" GET /api/image/:imageId: description: 获取图片(代理/签名URL) response: 200: Binary (image/*) 404: Not Found POST /api/admin/switch-model: description: 切换模型(管理接口) headers: Authorization: Bearer request: type: "vision" | "imageGen" provider: string response: 200: success: true activeProvider: string ``` ### 7.2 错误码规范 ```typescript // constants/errorCodes.ts export const ErrorCodes = { // 图片相关 (1xxx) INVALID_IMAGE_FORMAT: { code: 1001, message: '不支持的图片格式,请上传 JPG/PNG/WebP' }, IMAGE_TOO_LARGE: { code: 1002, message: '图片过大,请上传 10MB 以内的图片' }, IMAGE_TOO_SMALL: { code: 1003, message: '图片分辨率过低,请上传更清晰的照片' }, NO_FACE_DETECTED: { code: 1004, message: '未检测到人脸,请上传包含清晰正面人脸的照片' }, MULTIPLE_FACES: { code: 1005, message: '检测到多张人脸,请上传单人照片' }, FACE_TOO_SMALL: { code: 1006, message: '人脸区域过小,请上传脸部更清晰的照片' }, // 任务相关 (2xxx) TASK_NOT_FOUND: { code: 2001, message: '任务不存在' }, TASK_EXPIRED: { code: 2002, message: '任务已过期' }, TASK_IN_PROGRESS: { code: 2003, message: '任务处理中,请稍候' }, // AI 模型相关 (3xxx) VISION_ANALYSIS_FAILED: { code: 3001, message: 'AI 分析失败,请重试' }, IMAGE_GENERATION_FAILED: { code: 3002, message: '图像生成失败,请重试' }, MODEL_UNAVAILABLE: { code: 3003, message: 'AI 服务暂时不可用' }, CONTENT_POLICY_VIOLATION: { code: 3004, message: '图片内容不符合使用规范' }, // 系统相关 (4xxx) RATE_LIMITED: { code: 4001, message: '请求过于频繁,请稍后再试' }, SERVER_ERROR: { code: 4002, message: '服务器错误,请稍后重试' }, SERVICE_UNAVAILABLE: { code: 4003, message: '服务维护中' }, } as const; ``` --- ## 8. 数据模型 ### 8.1 任务模型 ```typescript // types/task.ts interface Task { id: string; // UUID status: TaskStatus; progress: number; // 0-100 // 输入 originalImage: { path: string; url: string; mimeType: string; size: number; }; // Vision 分析结果 analysis?: { imagePrompt: string; reasoning: string; rawResponse: string; completedAt: Date; }; // 生成结果 generation?: { imageUrl: string; localPath: string; revisedPrompt?: string; completedAt: Date; }; // 错误信息 error?: { code: number; message: string; details?: string; stage: 'upload' | 'analysis' | 'generation'; }; // 元数据 createdAt: Date; updatedAt: Date; expiresAt: Date; // 默认 24h 后过期 // 配置快照(记录使用的模型) modelConfig: { visionProvider: string; imageGenProvider: string; }; type TaskStatus = | 'pending' | 'analyzing' | 'generating' | 'completed' | 'failed'; ``` ### 8.2 存储策略 ```text 本地存储结构: uploads/ ├── tasks/ │ ├── {taskId}/ │ │ ├── original.jpg # 原图 │ │ ├── generated.jpg # 生成图 │ │ └── metadata.json # 任务元数据 │ └── ... └── temp/ # 临时文件(处理中) 清理策略: - 已完成任务: 24 小时后清理 - 失败任务: 6 小时后清理 - 临时文件: 1 小时后清理 ``` --- ## 9. 前端设计 ### 9.1 页面组件结构 ```text src/ ├── app/ │ ├── page.tsx # 首页 │ ├── result/[taskId]/ │ │ └── page.tsx # 结果页 │ └── layout.tsx ├── components/ │ ├── upload/ │ │ ├── DropZone.tsx # 拖拽上传区 │ │ ├── ImagePreview.tsx # 上传预览 │ │ └── UploadButton.tsx │ ├── progress/ │ │ ├── ProgressBar.tsx # 进度条 │ │ ├── StatusMessage.tsx # 状态文字 │ │ └── LoadingAnimation.tsx │ ├── result/ │ │ ├── BeforeAfter.tsx # 前后对比 │ │ ├── ReasoningCard.tsx # 造型说明 │ │ └── ActionButtons.tsx # 下载/分享/重试 │ └── common/ │ ├── Header.tsx │ ├── Footer.tsx │ └── ErrorBoundary.tsx ├── hooks/ │ ├── useUpload.ts │ ├── useTaskStatus.ts # 轮询任务状态 │ └── useImagePreload.ts ├── lib/ │ ├── api.ts # API 调用封装 │ └── utils.ts └── styles/ └── globals.css ``` ### 9.2 状态管理流程 ```typescript // hooks/useTaskStatus.ts interface TaskState { taskId: string | null; status: TaskStatus; progress: number; message: string; result: TaskResult | null; error: TaskError | null; } function useTaskStatus(taskId: string | null) { const [state, setState] = useState(initialState); useEffect(() => { if (!taskId) return; const pollInterval = setInterval(async () => { const response = await api.getTaskStatus(taskId); setState(prev => ({ ...prev, status: response.status, progress: response.progress, message: getStatusMessage(response.status), })); if (response.status === 'completed') { clearInterval(pollInterval); const result = await api.getTaskResult(taskId); setState(prev => ({ ...prev, result })); } if (response.status === 'failed') { clearInterval(pollInterval); setState(prev => ({ ...prev, error: response.error })); } }, 2000); return () => clearInterval(pollInterval); }, [taskId]); return state; } ``` --- ## 10. 安全与性能 ### 10.1 安全措施 | **风险** | **措施** | | --- | --- | | 恶意文件上传 | 文件类型白名单 + magic bytes 校验 | | 图片内容违规 | 可接入内容审核 API(腾讯云/阿里云) | | API 滥用 | 基于 IP 的速率限制 + 可选验证码 | | 敏感数据泄露 | 任务 ID 使用 UUID、图片 URL 签名 | | 隐私保护 | 默认 24h 自动清理、不做持久化存储 | ### 10.2 性能优化 | **场景** | **策略** | | --- | --- | | 图片上传 | 前端压缩至 2048px max、使用 WebP | | 长任务等待 | 轮询间隔动态调整(2s → 5s) | | 结果页加载 | 图片渐进式加载 + 骨架屏 | | API 响应 | 压缩响应、CDN 加速静态资源 | --- ## 11. 部署架构 ### 11.1 开发环境 ```text 本地开发: - Node.js 18+ - pnpm - 本地文件存储 ``` ### 11.2 生产环境 ```text ┌─────────────────────────────────────────────────────────┐ │ CDN │ │ (静态资源) │ └──────────────────────────┬──────────────────────────────┘ │ ┌──────────────────────────▼──────────────────────────────┐ │ Load Balancer │ │ (Nginx/云LB) │ └──────────────────────────┬──────────────────────────────┘ │ ┌──────────────────┼──────────────────┐ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ App Node │ │ App Node │ │ App Node │ │ (PM2) │ │ (PM2) │ │ (PM2) │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ └────────────────┬┼─────────────────┘ ││ ┌───────────────┘└───────────────┐ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ Redis │ │ S3/MinIO │ │ (任务状态) │ │ (文件存储) │ └──────────────┘ └──────────────┘ ``` --- **VibeCoding 导航**:⬅️ [[02-页面清单|02-页面清单]] | 01-造型生成网站 - 设计稿 | ➡️ [[02-计划书|02-计划书]]